﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cofe.Core;
using Cofe.Core.Filter;
using Cofe.Core.Interfaces;
using Cofe.Core.Property;
using Cofe.Core.Service;

namespace Cofe.Data
{
    public class RepositoryActionProvider : PropertyProviderBase<IPropertyHost>
    {
        public static string DatabaseUpdaterKey = "DatabaseUpdater";

        #region Factory
        [Export(typeof(ICofeService))]
        [ServicePriority(ServicePriorityAttribute.DefaultPriority_COFEDB)]
        public class Factory : ISecondaryPropertyProviderFactory, INotifyImported
        {
            public IEnumerable<IPropertyProvider> GetSecoondaryPropertyProviders(IPropertyHost propertyHost)
            {
                if (_repositoryFactory != null &&
                    !(propertyHost.SecondaryPropertyProviders.Any((p) => p is RepositoryActionProvider)))
                    yield return new RepositoryActionProvider(propertyHost, _repositoryFactory);
            }

            public void OnImported(IServiceLocater serviceLocater)
            {
                _repositoryFactory = serviceLocater.TryFindService<IRepositoryFactory>();
            }

            private IRepositoryFactory _repositoryFactory = null;
            private IServiceLocater _serviceLocater = null;

            public IServiceLocater ServiceLocater
            {
                get { return _serviceLocater ?? CofeServices.ServiceLocater; }
                set { _serviceLocater = value; }
            }



        }
        #endregion

        #region Constructor

        public RepositoryActionProvider(IPropertyHost propertyHost, IRepositoryFactory repositoryFactory)
            : base(propertyHost,
            Cofe.Data.PropertyDefinitions.DatabaseDefinitions,
            Cofe.Core.PropertyDefinitions.CofeDefinitions)
        {
            _repositoryFactory = repositoryFactory;

            registerActionAsync(DatabaseProperties.WriteRepositoryAsync,
                (pm) => writeRepositoryAsync(pm));
            registerActionAsync(CofeProperties.RefreshAsync,
                (pm) => refreshAsync(pm));
        }

        #endregion

        #region Methods

        private Task refreshAsync(ParameterDic parameters)
        {
            return CofeServices.TaskManager.TaskConstructor.CreateTask(base.Entity, CofeProperties.RefreshAsync,
            (ph) =>
            {
                if (base.Entity.Behaviors.IsPropertySupported(CacheProperties.Id))
                {
                    var id = base.Entity.Behaviors.GetProperty<Guid>(CacheProperties.Id);
                    if (id != CofeConstants.RootGuid)
                        using (var repository = _repositoryFactory.CreateRepository())
                        {
                            repository.Expire(id);
                            repository.SaveChanges();
                        }
                    parameters.IsHandled = true;

                }
            });
        }

        private async Task writeRepositoryAsync(ParameterDic parameters)
        {
            using (var ctx = CofeServices.SecurityManager
                .ImpersonateAsService(SecurityConstants.ServiceType_WriteRepositoryAsync))
            {
                try
                {
                    IPropertyHost baseEntity = base.Entity;
                    Guid id = base.Entity.Behaviors.GetProperty<Guid>(CacheProperties.Id);
                    parameters.IsHandled = true;

                    var cofeAttribute = baseEntity.Behaviors.GetProperty<CofeAttributes>(CofeProperties.CofeAttributes);
                    if (cofeAttribute.HasFlag(CofeAttributes.NotCachable))
                        return;

                    try
                    {
                        await baseEntity.ConnectAsync();
                    }
                    catch (System.IO.FileNotFoundException ex)
                    {

                        using (var repository = _repositoryFactory.CreateRepository())
                        {
                            Guid parentId = base.Entity.Behaviors.GetProperty<Guid>(CacheProperties.ParentId);
                            repository.Remove(FilterCollections.FromGuid(id));
                            repository.Refresh(FilterCollections.FromGuid(parentId), false, true);
                            repository.SaveChanges();
                        }
                        return;
                    }

                    if (parameters.UpdateCurrentEntry)
                    {
                        IPropertyHost clone = CofeServices.EntryConstructor.ConstructPropertyHost(baseEntity.PrimaryPropertyProvider);
                        using (var repository = _repositoryFactory.CreateRepository())
                        {
                            var retDbEntry = repository.AddOrUpdate(true, false, clone);                            
                            repository.SaveChanges();
                            if (id == Guid.Empty && retDbEntry.Count() == 1)
                            {
                                if (retDbEntry.First().Id == Guid.Empty)
                                    throw new Exception();
                                baseEntity.PropertyCache.CacheProperty(CacheProperties.Id, retDbEntry.First().Id);
                            }
                        }
                    }

                    

                    if (parameters.UpdateSubEntries && baseEntity.Behaviors.IsPropertySupported(CofeDirectoryProperties.List))
                    {
                        if (CofeServices.CofeSettings.CacheUpdateOptions.HasFlag(CacheUpdateOptions.Active) &&
                            parameters.Sender != RepositoryActionProvider.DatabaseUpdaterKey)
                        {
                            //Active update, thus update using EFDatabaseUpdater, but current call isnt send from 
                            //EFDatabaseUpdater (!=  DatabaseUpdaterKey), so ignore UpdateSubEntries.
                            return;
                        }
                        else
                        {
                            string parseName = baseEntity.Behaviors.GetProperty<string>(CofeProperties.FullParseName);
                            var subEntryList = (await baseEntity.Behaviors.InvokeAsync<IEnumerable<ICofeSystemInfo>>(CofeDirectoryProperties.ListAsync,
                                new ParameterDic() { EnableCache = false })
                                ).Select(e => e.Properties);

                            using (var repository = _repositoryFactory.CreateRepository())
                            {
                                repository.AddOrUpdate(true, true, subEntryList.ToArray());
                                repository.Refresh(FilterCollections.FromParseName(parseName), false, true);
                                repository.SaveChanges();
                            }

                            if (parameters.Recursive)
                            {
                                foreach (var subEntry in subEntryList)
                                {
                                    var actualSubEntry = subEntry;
                                    if (subEntry.Behaviors.IsPropertySupported(CofeEntryLinkProperties.LinkedEntryPH))
                                        actualSubEntry = subEntry.Behaviors.GetProperty<IPropertyHost>(CofeEntryLinkProperties.LinkedEntryPH);

                                    if (actualSubEntry.SupportedDefinitions.Contains(
                                        Cofe.Core.PropertyDefinitions.CofeDirDefinitions, PropertyDefinitionComparer.CompareCategory))
                                        if (actualSubEntry.Behaviors.IsPropertySupported(DatabaseProperties.WriteRepositoryAsync))
                                            await actualSubEntry.Behaviors.InvokeAsync(DatabaseProperties.WriteRepositoryAsync,
                                                new ParameterDic() { UpdateCurrentEntry = false, UpdateSubEntries = true, Recursive = true });
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    CofeServices.Logger.Log(ex);

                    //Make it refreshed, so it wont run again soon.
                    using (var repository = _repositoryFactory.CreateRepository())
                    {
                        Guid id = base.Entity.Behaviors.GetProperty<Guid>(CacheProperties.Id);
                        repository.Refresh(FilterCollections.FromGuid(id), true, true);
                        repository.SaveChanges();
                    }
                    //throw ex;
                }

            }
        }

        #endregion

        #region Data

        private IRepositoryFactory _repositoryFactory = null;

        #endregion

        #region Public Properties

        /// <summary>
        /// For Debug only.
        /// </summary>
        public ISecondaryPropertyProviderFactory[] SecondaryPropertyProviderFactories { get; set; }

        /// <summary>
        /// For Debug only.
        /// </summary>
        public IPathParser[] PathParsers { get; set; }

        public IEntryConstructor EntryConstructor { get; internal set; }

        #endregion

    }
}
